using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Attributes;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Converter.Block
{
    public class BinaryExpressionConverter : BlockNodeFactory
    {
        

        #region Convert Binary Expression
        /// <summary>
        /// TODO: R-MembAcc1 ? Check if left side is option and if null????
        /// R-MembAcc2
        /// Converts the Binary Expression
        /// </summary>
        /// <param name="blockSyntax"></param>
        /// <param name="nodeToReplaceInBlock"></param>
        /// <returns></returns>
        public override List<StatementSyntax> ConvertBlockNode()
        {
            var statementsToAdd = new List<StatementSyntax>();

            // var expressionStatement = (ExpressionStatementSyntax)nodeToReplaceInBlock.Statement;
            var binaryExpression = NodeToReplaceInBlock.ReferencedNode as BinaryExpressionSyntax;
            if (!(binaryExpression.Left is IdentifierNameSyntax) || !(binaryExpression.Right is ExpressionSyntax))
                return null;

            if (binaryExpression.Kind == SyntaxKind.EqualsExpression)
                return ConvertEqualsExpression();

            //First check the leftside of the node for the multiplicity. If it is not Any, we can return
            var leftIdentifier = (IdentifierNameSyntax)binaryExpression.Left;
            var leftSideNode = SemanticModel.FindDecleration(leftIdentifier);
            var leftSideMultilicity = Utilities.GetMultiplicityAttribute(leftSideNode);
            if (leftSideMultilicity != AttributeType.Any) return null;

            //Now check the right side. Depending on the multiplicity of the right side we will choose a proper replacement
            ExpressionSyntax expression = (ExpressionSyntax)binaryExpression.Right;
            CommonSyntaxNode declerationNode = null;
            if (expression is IdentifierNameSyntax)
                declerationNode = SemanticModel.FindDecleration((IdentifierNameSyntax)expression);
            var multiplicityType = Utilities.GetMultiplicityAttribute(declerationNode);

            //Now start with the transformation
            //In Case of add assign += we don't want to delete the list, but we want to add the item
            if (NodeToReplaceInBlock.ReferencedNode.Kind == SyntaxKind.AddAssignExpression)
            {
                statementsToAdd = ConvertAddAssignExpression( multiplicityType, leftIdentifier, expression);
            }
            //Here we remove the item from the list
            else if (NodeToReplaceInBlock.ReferencedNode.Kind == SyntaxKind.SubtractAssignExpression)
            {
                statementsToAdd = ConvertSubtractAssignExpression(multiplicityType, leftIdentifier, expression);
            }
            //and here we basically clear the list and then add the same code as in addassignexpression
            else
            {
                string tempVariable = VariableCreatorService.GetTempVariable();
                StatementSyntax clearStatement = Syntax.ParseStatement(string.Format("{0}.Clear();" + Environment.NewLine, leftIdentifier.ToFullString()));
                statementsToAdd.Add(clearStatement);
                statementsToAdd.AddRange(ConvertAddAssignExpression( multiplicityType, leftIdentifier, expression));
            }

            return statementsToAdd;

        }

        /// <summary>
        /// Implements the rules for R-Ident1, R-Ident2, R-Ident3 and R-Ident4
        /// </summary>
        /// <param name="blockSyntax"></param>
        /// <param name="nodeToReplaceInBlock"></param>
        /// <returns></returns>
        private List<StatementSyntax> ConvertEqualsExpression()
        {
            
            var statementsToAdd = new List<StatementSyntax>();
            var binaryExpression = NodeToReplaceInBlock.ReferencedNode as BinaryExpressionSyntax;

            //First check the leftside of the node for the multiplicity. If it is not Any, we can return
            var leftIdentifier = (IdentifierNameSyntax)binaryExpression.Left;
            var leftSideNode = SemanticModel.FindDecleration(leftIdentifier);
            var leftSideMultiplicity = Utilities.GetMultiplicityAttribute(leftSideNode);

            //Secondly check the right side of the node for multiplicity
            var rightIdentifier = (IdentifierNameSyntax)binaryExpression.Right;
            var rightSideNode = SemanticModel.FindDecleration(rightIdentifier);
            var rightSideMultiplicity = Utilities.GetMultiplicityAttribute(rightSideNode);

            var result = VariableCreatorService.GetTempVariable();
            var newIdentifier = Syntax.IdentifierName(result);
            var tempLeft = VariableCreatorService.GetTempVariable();
            var tempRight = VariableCreatorService.GetTempVariable();
            //R-Ident1
            if (leftSideMultiplicity == AttributeType.Option && rightSideMultiplicity == AttributeType.Option)
            {
                // statementsToAdd.Add(NodeToReplaceInBlock.Statement);
                //Dont change anything
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.Statement, NodeToReplaceInBlock.Statement);
                //StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.Statement, NodeToReplaceInBlock.Statement));
            }
            else
            {
                var assignTempLeft = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempLeft, binaryExpression.Left));
                var assignTempRight = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempRight, binaryExpression.Right));
                StatementSyntax assignResult;
                if (leftSideMultiplicity == AttributeType.Any && rightSideMultiplicity == AttributeType.Option)
                {
                    //@Text : IsEmpty does not exist, but we could use first with the same effect. Has also the advantage that we don't need to clone
                    //var assignResult = Syntax.ParseStatement(string.Format("{0}.Count == 0 ? {1} == null : {0}.Clone().Remove({1})" + Environment.NewLine,
                    //    tempLeft,tempRight));
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == 0 ? {2} == null : {1}.First() == {2};" + Environment.NewLine,
                        result, tempLeft, tempRight));
                }
                else if (leftSideMultiplicity == AttributeType.Option && rightSideMultiplicity == AttributeType.Any)
                {
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == 0 ? {2} == null : {1}.First() == {2};" + Environment.NewLine,
                        result, tempRight, tempLeft));
                }
                else
                {
                    //@Text : Here the Linq Method Except is used instead of cloning and deleting the list
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == {2}.Count && {1}.Except({2}).Count() == 0; " + Environment.NewLine,
                        result, tempLeft, tempRight));
                }

                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, newIdentifier);
       
                // StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, newIdentifier));

                statementsToAdd.AddRange(assignTempLeft, assignTempRight, assignResult);
            }
            return statementsToAdd;


        }

        /// <summary>
        /// Implements rule (M-SUB1)
        /// </summary>
        /// <param name="newBlock">the block where the statements are added</param>
        /// <param name="rightSideMultiplicity">multiplicity of the right side </param>
        /// <param name="leftIdentifier">identifier of the left side (the a)</param>
        /// <param name="rightExpression">Expression of the right side (the b or a constuctor call for instance)</param>
        /// <returns>list of statements which will be replaced</returns>
        private List<StatementSyntax> ConvertSubtractAssignExpression(AttributeType rightSideMultiplicity,
            IdentifierNameSyntax leftIdentifier, ExpressionSyntax rightExpression)
        {
            var statementsToAdd = new List<StatementSyntax>();
            string loopVariable = VariableCreatorService.GetTempVariable();
            if (rightSideMultiplicity == AttributeType.Any)
            {
                string resultVariable = VariableCreatorService.GetTempVariable();
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, leftIdentifier.ToString()));
                var removeLoop = Syntax.ParseStatement(string.Format("foreach (var {0} in {1}) {2}.Remove({0});" + Environment.NewLine,
                    loopVariable, rightExpression.ToString(), resultVariable.ToString()));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", leftIdentifier, resultVariable));
                statementsToAdd.AddRange(resultStatementInit, removeLoop);
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, expressionReplacement);
              //  StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, expressionReplacement));
               // statementsToAdd.Add(removeLoop);
            }
            //Option, Bare te
            else
            {
                string newVariable = VariableCreatorService.GetTempVariable();
                string resultVariable = VariableCreatorService.GetTempVariable();
                StatementSyntax initializeStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, newVariable, rightExpression.ToString()));
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, leftIdentifier.ToString()));
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Remove({0});" + Environment.NewLine,
                    newVariable, leftIdentifier.ToString()));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", leftIdentifier, resultVariable));

                statementsToAdd.AddRange(initializeStatement, resultStatementInit, conditionStatement);
                //statementsToAdd.Add(conditionStatement);
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, expressionReplacement);
               // StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, expressionReplacement));

            }
            return statementsToAdd;
        }

        /// <summary>
        /// (M-ADD1)
        /// Applys the rules to add a new element aka a += b
        /// </summary>
        /// <param name="newBlock">the block where the statements are added</param>
        /// <param name="rightSideMultiplicity">multiplicity of the right side </param>
        /// <param name="leftIdentifier">identifier of the left side (the a)</param>
        /// <param name="rightExpression">Expression of the right side (the b or a constuctor call for instance)</param>
        /// <returns>list of statements which will be replaced</returns>
        private List<StatementSyntax> ConvertAddAssignExpression(AttributeType rightSideMultiplicity,
            IdentifierNameSyntax leftIdentifier, ExpressionSyntax rightExpression)
        {
            var statementsToAdd = new List<StatementSyntax>();

            //If we are accessing an "any" field on the right side we want to addrange otherwie we just want to check the element and add it
            if (rightSideMultiplicity == AttributeType.Any)
            {
                var replacement = Syntax.ParseExpression(string.Format("{0}.AddRange({1})", leftIdentifier.ToString(), rightExpression.ToString()));
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, replacement);
             //   StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, replacement));

            }
            //Option, Bare te
            else
            {
                string newVariable = VariableCreatorService.GetTempVariable();
                string resultVariable = VariableCreatorService.GetTempVariable();
                //@Text:Because identifier might be a property which has site effects
                var initializeStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, newVariable, rightExpression.ToString()));
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, leftIdentifier.ToString()));
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Add({0});" + Environment.NewLine,
                    newVariable, resultVariable));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", leftIdentifier, resultVariable));
                //NodeToReplaceInBlock.Statement
               // var expressionReplacement = Syntax.ParseExpression(string.Format("{0} != null ? {1}.Add({0}) : {1};"));
                statementsToAdd.AddRange(initializeStatement, resultStatementInit, conditionStatement);
                StatementReplacements = new StatementReplacement<CommonSyntaxNode>(NodeToReplaceInBlock.ReferencedNode, expressionReplacement);
             //   StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.ReferencedNode, expressionReplacement));

            }
            return statementsToAdd;
        }
        #endregion
    }
}
